package ldh.msg.ui.helper;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.URL;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineEvent;
import javax.sound.sampled.LineListener;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import ldh.msg.helper.MsgConstants;

public class SoundPlayer extends JPanel implements Runnable, LineListener, MetaEventListener, ActionListener {

    final int bufSize = 16384;

//    Vector<File> sounds = new Vector<File>();
    File sndFile = null;
    Thread thread;
    Sequencer sequencer;
    boolean midiEOM, audioEOM;
    Synthesizer synthesizer;
    MidiChannel channels[]; 
    Object currentSound;
    String currentName;
    double duration;
    int num;
    boolean bump;
    boolean paused = false;
    JButton startB, pauseB;
    String errStr;
    Icon startIcon = null;
    Icon stopIcon = null;
    Icon pauseIcon = null;
    Icon resumeIcon = null;

    private double gain;
    public SoundPlayer() {
        setLayout(new FlowLayout(FlowLayout.LEFT, 1, 1));        
        startIcon = new ImageIcon(MsgConstants.DIR_DSP + "play.png");
        stopIcon = new ImageIcon(MsgConstants.DIR_DSP + "stop.png");
        pauseIcon = new ImageIcon(MsgConstants.DIR_DSP + "pause.png");
        resumeIcon = new ImageIcon(MsgConstants.DIR_DSP + "resume.png");
        
        startB = new JButton();
        startB.setName("Start");
        startB.setIcon(startIcon);
        startB.setPreferredSize(new Dimension(20, 18));
        startB.setEnabled(false);
        startB.addActionListener(this);
        
        pauseB = new JButton();
        pauseB.setName("Pause");
        pauseB.setIcon(pauseIcon);
        pauseB.setPreferredSize(new Dimension(20, 18));
        pauseB.setEnabled(false);
        pauseB.addActionListener(this);
        add(startB);
        add(pauseB);
    }

    public void open(String fileName) {
        try {
        	loadJuke(fileName);
        	
            sequencer = MidiSystem.getSequencer();
			if (sequencer instanceof Synthesizer) {
				synthesizer = (Synthesizer)sequencer;
				channels = synthesizer.getChannels();
			}
        } catch (Exception ex) { ex.printStackTrace(); return; }
        sequencer.addMetaEventListener(this);
    }


    public void close() {
        if (thread != null && startB != null) {
            startB.doClick(0);
        }
        if (sequencer != null) {
            sequencer.close();
        }
    }

    public void loadJuke(String name) {
        try {
        	if (name.endsWith(".au") || name.endsWith(".rmf") ||
                  name.endsWith(".mid") || name.endsWith(".wav") ||
                  name.endsWith(".aif") || name.endsWith(".aiff")) {
	            File file = new File(name);
	            if (file != null && file.exists()) {
	            	startB.setEnabled(true);
	                sndFile = file;
	            } else {
	            	startB.setEnabled(false);
	            }
        	}
        } catch (SecurityException ex) {
            reportStatus(ex.toString());
        } catch (Exception ex) {
            reportStatus(ex.toString());
        }
    }

    public boolean loadSound(Object object) {
        duration = 0.0;

        if (object instanceof URL) {
            currentName = ((URL) object).getFile();
            try {
                currentSound = AudioSystem.getAudioInputStream((URL) object);
            } catch(Exception e) {
                try { 
                    currentSound = MidiSystem.getSequence((URL) object);
		} catch (InvalidMidiDataException imde) {
		    System.out.println("Unsupported audio file.");
		    return false;
                } catch (Exception ex) {
                    ex.printStackTrace();
		    currentSound = null;
		    return false;
                }
            }
        } else if (object instanceof File) {
            currentName = ((File) object).getName();
            try {
                currentSound = AudioSystem.getAudioInputStream((File) object);
            } catch(Exception e1) {
                try { 
                    FileInputStream is = new FileInputStream((File) object);
                    currentSound = new BufferedInputStream(is, 1024);
                } catch (Exception e3) { 
                    e3.printStackTrace(); 
					currentSound = null;
					return false;
                }
            }
        }
        if (sequencer == null) {
            currentSound = null;
            return false;
        } 

        if (currentSound instanceof AudioInputStream) {
           try {
                AudioInputStream stream = (AudioInputStream) currentSound;
                AudioFormat format = stream.getFormat();

                if ((format.getEncoding() == AudioFormat.Encoding.ULAW) ||
                    (format.getEncoding() == AudioFormat.Encoding.ALAW)) 
                {
                    AudioFormat tmp = new AudioFormat(
                                              AudioFormat.Encoding.PCM_SIGNED, 
                                              format.getSampleRate(),
                                              format.getSampleSizeInBits() * 2,
                                              format.getChannels(),
                                              format.getFrameSize() * 2,
                                              format.getFrameRate(),
                                              true);
                    stream = AudioSystem.getAudioInputStream(tmp, stream);
                    format = tmp;
                }
                DataLine.Info info = new DataLine.Info(
                                          Clip.class, 
                                          stream.getFormat(), 
                                          ((int) stream.getFrameLength() *
                                              format.getFrameSize()));

                Clip clip = (Clip) AudioSystem.getLine(info);
                clip.addLineListener(this);
                clip.open(stream);
                currentSound = clip;
            } catch (Exception ex) { 
				ex.printStackTrace(); 
				currentSound = null;
				return false;
		    }
        } else if (currentSound instanceof Sequence || currentSound instanceof BufferedInputStream) {
            try {
                sequencer.open();
                if (currentSound instanceof Sequence) {
                    sequencer.setSequence((Sequence) currentSound);
                } else {
                    sequencer.setSequence((BufferedInputStream) currentSound);
                }

            } catch (InvalidMidiDataException imde) { 
				System.out.println("Unsupported audio file.");
				currentSound = null;
				return false;
            } catch (Exception ex) { 
				ex.printStackTrace(); 
				currentSound = null;
				return false;
            }
        }		
        duration = getDuration();

        return true;
    }

    public void playSound() {
        midiEOM = audioEOM = bump = false;
        if (currentSound instanceof Sequence || currentSound instanceof BufferedInputStream && thread != null) {
        	System.out.println("CurrentSound Sequence");
            sequencer.start();
            while (!midiEOM && thread != null && !bump) {
                try { thread.sleep(99); } catch (Exception e) {break;}
            }
            sequencer.stop();
            sequencer.close();
        } else if (currentSound instanceof Clip && thread != null) {
        	System.out.println("CurrentSound Clip volume : " + this.gain);
            Clip clip = (Clip) currentSound;
            FloatControl gainControl = (FloatControl)clip.getControl(FloatControl.Type.MASTER_GAIN);
        	float dB = (float)(Math.log(gain)/Math.log(10.0)*20.0);
        	gainControl.setValue(dB);
            clip.start();
            try { thread.sleep(99); } catch (Exception e) { }
            while ((paused || clip.isActive()) && thread != null && !bump) {
                try { thread.sleep(99); } catch (Exception e) {break;}
            }
            clip.stop();
            clip.close();
        }
        currentSound = null;
    }


    public double getDuration() {
        double duration = 0.0;
        if (currentSound instanceof Sequence) {
            duration = ((Sequence) currentSound).getMicrosecondLength() / 1000000.0;
        }  else if (currentSound instanceof BufferedInputStream) {
			duration = sequencer.getMicrosecondLength() / 1000000.0;
		} else if (currentSound instanceof Clip) {
            Clip clip = (Clip) currentSound;
            duration = clip.getBufferSize() / 
                (clip.getFormat().getFrameSize() * clip.getFormat().getFrameRate());
        } 
        return duration;
    }

    public double getSeconds() {
        double seconds = 0.0;
        if (currentSound instanceof Clip) {
            Clip clip = (Clip) currentSound;
            seconds = clip.getFramePosition() / clip.getFormat().getFrameRate();
        } else if ( (currentSound instanceof Sequence) || (currentSound instanceof BufferedInputStream) ) {
            try {
                seconds = sequencer.getMicrosecondPosition() / 1000000.0;
            } catch (IllegalStateException e){
                System.out.println("TEMP: IllegalStateException "+
                    "on sequencer.getMicrosecondPosition(): " + e);
            }
        }
        return seconds;
    }

    public void setVolume(int gain) {
    	
    	this.gain = ((double)gain) / 100;
    	System.out.println("setVolume volume : " + this.gain);
    }
    
    public void update(LineEvent event) {
        if (event.getType() == LineEvent.Type.STOP && !paused) { 
            audioEOM = true;
        }
    }

    public void meta(MetaMessage message) {
        if (message.getType() == 47) {  // 47 is end of track
            midiEOM = true;
        }
    }

    private void reportStatus(String msg) {
        if ((errStr = msg) != null) {
            System.out.println(errStr);
        }
    }

    public Thread getThread() {
        return thread;
    }

    public void start() {
        thread = new Thread(this);
        thread.setName("Juke");
        thread.start();
    }

    private void stop() {
        if (thread != null) {
            thread.interrupt();
        }
        thread = null;
    }
    
    public void stopPlayer() {
    	paused = false;
        stop();
        startB.setName("Start");
        startB.setIcon(startIcon);
        pauseB.setName("Pause");
        pauseB.setIcon(pauseIcon);
        setComponentsEnabled(false);
    }

    public void run() {
        if( loadSound(sndFile) == true ) {
            playSound();
        }
        num = 0;

        if (thread != null) {
            startB.doClick();
        }
        thread = null;
        currentName = null;
        currentSound = null;
    }
//    private JButton addButton(String name, JPanel panel, boolean state) {
//        JButton b = new JButton(name);
//        b.addActionListener(this);
//        b.setEnabled(state);
//        panel.add(b);
//        return b;
//    }

    public void setComponentsEnabled(boolean state) {
        pauseB.setEnabled(state);
    }

    public void actionPerformed(ActionEvent e) {
        JButton button = (JButton) e.getSource();
        if (button.getName().equals("Start")) {
            
            paused = false;
            num = num == -1 ? 0 : num;
            start();
//            button.setText("Stop");
            startB.setName("Stop");
            startB.setIcon(stopIcon);
            setComponentsEnabled(true);
        } else if (button.getName().equals("Stop")) {
           
            paused = false;
            stop();
            startB.setName("Start");
            startB.setIcon(startIcon);
            pauseB.setName("Pause");
            pauseB.setIcon(pauseIcon);
            setComponentsEnabled(false);
        } else if (button.getName().equals("Pause")) {
            paused = true;
            if (currentSound instanceof Clip) {
                ((Clip) currentSound).stop();
            } else if (currentSound instanceof Sequence || currentSound instanceof BufferedInputStream) {
                sequencer.stop();
            }
            pauseB.setName("Resume");
            pauseB.setIcon(resumeIcon);
        } else if (button.getName().equals("Resume")) {
            paused = false;
            if (currentSound instanceof Clip) {
                ((Clip) currentSound).start();
            } else if (currentSound instanceof Sequence || currentSound instanceof BufferedInputStream) {
                sequencer.start();
            }
            pauseB.setName("Pause");
            pauseB.setIcon(pauseIcon);
        }
    }

    public static void main(String args[]) {
        String media = "snd/Applause.wav";
        final SoundPlayer player = new SoundPlayer();
        player.open(media);
        JFrame f = new JFrame("Juke Box");
        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {System.exit(0);}
            public void windowIconified(WindowEvent e) {
            }
        });
        f.getContentPane().add("Center", player);
        f.pack();
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        int w = 220;
        int h = 100;
        f.setLocation(screenSize.width/2 - w/2, screenSize.height/2 - h/2);
        f.setSize(w, h);
        f.setVisible(true);
        if (args.length > 0) {
            File file = new File(args[0]);
            if (file == null && !file.isDirectory()) {
                System.out.println("usage: java Juke audioDirectory");
            } 
        }
    }
} 
